package org.jenkinsci.plugins.pipeline.maven.listeners;

import static org.jenkinsci.plugins.pipeline.maven.dao.MonitoringPipelineMavenPluginDaoDecorator.registerCacheStatsSupplier;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.model.Item;
import hudson.model.Run;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jenkinsci.plugins.pipeline.maven.GlobalPipelineMavenConfig;
import org.jenkinsci.plugins.pipeline.maven.MavenArtifact;
import org.jenkinsci.plugins.pipeline.maven.dao.CacheStats;

public class DaoHelper {

    private static final Logger LOGGER = Logger.getLogger(DownstreamPipelineTriggerRunListener.class.getName());

    private static final AtomicInteger GET_GENERATED_ARTIFACTS_HITS = new AtomicInteger();
    private static final AtomicInteger GET_GENERATED_ARTIFACTS_MISSES = new AtomicInteger();

    private static final AtomicInteger LIST_DOWNSTREAM_JOBS_HITS = new AtomicInteger();
    private static final AtomicInteger LIST_DOWNSTREAM_JOBS_MISSES = new AtomicInteger();

    static {
        registerCacheStatsSupplier(() -> new CacheStats(
                "getGeneratedArtifacts", GET_GENERATED_ARTIFACTS_HITS.get(), GET_GENERATED_ARTIFACTS_MISSES.get()));
        registerCacheStatsSupplier(() -> new CacheStats(
                "listDownstreamJobsByArtifact", LIST_DOWNSTREAM_JOBS_HITS.get(), LIST_DOWNSTREAM_JOBS_MISSES.get()));
    }

    private GlobalPipelineMavenConfig globalPipelineMavenConfig;

    // Memory for generatedArtifacts - the key is jobFullName#buildNumber
    private Map<String, List<MavenArtifact>> generatedArtifactsCache = new HashMap<>();

    private Map<String, Map<MavenArtifact, SortedSet<String>>> downstreamJobsByArtifact = new HashMap<>();

    public DaoHelper(GlobalPipelineMavenConfig globalPipelineMavenConfig) {
        super();
        this.globalPipelineMavenConfig = globalPipelineMavenConfig;
    }

    /**
     * Return the artifacts generated by the given build.
     *
     * @param jobFullName see {@link Item#getFullName()}
     * @param buildNumber see {@link Run#getNumber()}
     * @return sorted list of generated maven artifacts.
     */
    @NonNull
    List<MavenArtifact> getGeneratedArtifacts(@NonNull String jobFullName, int buildNumber) {
        String key = jobFullName + '#' + buildNumber;

        LOGGER.log(Level.FINER, "calling getGeneratedArtifacts {0} {1}, cache size: {2}", new Object[] {
            jobFullName, buildNumber, generatedArtifactsCache.size()
        });

        if (generatedArtifactsCache.containsKey(key)) {
            LOGGER.log(
                    Level.FINER, "cache hit for getGeneratedArtifacts {0} {1}", new Object[] {jobFullName, buildNumber
                    });
            GET_GENERATED_ARTIFACTS_HITS.incrementAndGet();
        } else {
            GET_GENERATED_ARTIFACTS_MISSES.incrementAndGet();
        }

        return generatedArtifactsCache.computeIfAbsent(
                key, k -> globalPipelineMavenConfig.getDao().getGeneratedArtifacts(jobFullName, buildNumber));
    }

    /**
     * List the downstream jobs who have a dependency on an artifact that has been
     * generated by the given build (build identified by the given
     * {@code jobFullName}, {@code buildNumber}).
     *
     * Doesn't return the passed job in case where a pipeline consumes an artifact
     * it also produces
     *
     * @param jobFullName see {@link Item#getFullName()}
     * @param buildNumber see {@link Run#getNumber()}
     * @return list of job full names (see {@link Item#getFullName()}) by
     *         {@link MavenArtifact}
     * @see Item#getFullName()
     */
    Map<MavenArtifact, SortedSet<String>> listDownstreamJobsByArtifact(String jobFullName, int buildNumber) {
        String key = jobFullName + '#' + buildNumber;
        if (downstreamJobsByArtifact.containsKey(key)) {
            LOGGER.log(Level.FINER, "cache hit for listDownstreamJobsByArtifact {0} {1}", new Object[] {
                jobFullName, buildNumber
            });
            LIST_DOWNSTREAM_JOBS_HITS.incrementAndGet();
        } else {
            LIST_DOWNSTREAM_JOBS_MISSES.incrementAndGet();
        }
        return downstreamJobsByArtifact.computeIfAbsent(
                key, k -> globalPipelineMavenConfig.getDao().listDownstreamJobsByArtifact(jobFullName, buildNumber));
    }
}
